package niu.toy.spring.mvc;

import cn.hutool.core.lang.Assert;
import cn.hutool.json.JSONUtil;

import niu.toy.spring.ioc.BeanFactory;
import niu.toy.spring.ioc.annotation.Nullable;
import niu.toy.spring.mvc.helper.ActionHelper;
import niu.toy.spring.mvc.impl.HandlerMappingFinder;
import niu.toy.spring.mvc.impl.InternalResourceViewResolver;
import niu.toy.spring.mvc.impl.RequestHandlerExecutor;
import niu.toy.spring.mvc.impl.argument.RequestBodyArgumentResolver;
import niu.toy.spring.mvc.impl.argument.RequestModelParamArgumentResolver;
import niu.toy.spring.mvc.impl.argument.RequestParamArgumentResolver;
import niu.toy.spring.mvc.impl.returnValue.ResponseBodyReturnValueHandler;
import niu.toy.spring.mvc.impl.returnValue.ViewNameReturnValueHandler;
import niu.toy.spring.util.WebUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * 处理请求：根据请求的url，找到对应的Action Handler 并执行，处理返回结果及输出
 */
@WebServlet(urlPatterns = "/*", loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet {
    private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);

    @Nullable
    private List<HandlerMapping> handlerMappings;

    @Nullable
    private List<HandlerExecutor> handlerExecutors;

    private List<ViewResolver> viewResolvers;

    private WebApplicationContext ctx;

    /**
     * 初始化Mapping Executor ViewResolver
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 初始化相关配置
        ServletContext servletContext = config.getServletContext();
        ctx=getWebApplicationContext(servletContext, WebApplicationContext.APPLICATION_CONTEXT_ROOT);
//        BeanFactory context=ctx.getBeanFactory();
        initStrategies();
    }
    public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
        Assert.notNull(sc, "ServletContext must not be null");
        Object attr = sc.getAttribute(attrName);
        if (attr == null) {
            return null;
        }
        if (attr instanceof RuntimeException) {
            throw (RuntimeException) attr;
        }
        if (attr instanceof Error) {
            throw (Error) attr;
        }
        if (attr instanceof Exception) {
            throw new IllegalStateException((Exception) attr);
        }
        if (!(attr instanceof WebApplicationContext)) {
            throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
        }
        return (WebApplicationContext) attr;
    }
    @Override
    @Nullable
    public String getServletName() {
        return DispatcherServlet.class.getName();
    }

    protected void initStrategies() {
        initHandlerMappings();
        initHandlerExecutors();
        initViewResolvers();
    }
    private void initHandlerMappings() {
        HandlerMappingFinder mappingFinder=new HandlerMappingFinder();
        this.handlerMappings= Arrays.asList(mappingFinder);
        try {
            mappingFinder.registerMapping(ActionHelper.getActionMap());
        }catch (Exception ex){
            logger.error("initHandlerMappings error!",ex);
        }
    }
    private void initHandlerExecutors() {
        RequestHandlerExecutor handlerExecutor=new RequestHandlerExecutor();
        this.handlerExecutors= Arrays.asList(handlerExecutor);
        handlerExecutor.getArgumentResolvers().addResolvers(new RequestParamArgumentResolver());
        handlerExecutor.getArgumentResolvers().addResolvers(new RequestModelParamArgumentResolver());
        handlerExecutor.getArgumentResolvers().addResolvers(new RequestBodyArgumentResolver());

//        handlerExecutor.getReturnValueHandlers().addHandler(new ResponseBodyReturnValueHandler());
        handlerExecutor.getReturnValueHandlers().addHandler(new ViewNameReturnValueHandler());
    }
    private void initViewResolvers() {
        viewResolvers=Arrays.asList(new InternalResourceViewResolver());
    }
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 设置请求编码方式
        request.setCharacterEncoding("UTF-8");
        String requestPath=WebUtil.getRequestPath(request);
        logger.debug("[ToySpring] {}:{} 当前线程:{}", request.getMethod(), requestPath,Thread.currentThread().getName());
        Exception dispatchException = null;
        HandlerExecutionChain handlerChain = null;
        ModelAndView mv = null;
        try {
            try {
                handlerChain = getHandler(request);
                if (handlerChain == null) {
                    response.sendError(HttpServletResponse.SC_NOT_FOUND);
                    return;
                }
                HandlerExecutor executor = getHandlerExecutor(handlerChain.getHandler());
                mv = executor.handle(request, response, handlerChain.getHandler());
                handlerChain.applyPostHandle(request, response, mv);
            } catch (Exception e) {
                dispatchException = e;
            }
            processDispatchResult(request, response, handlerChain, mv, dispatchException);
        }catch (Exception ex) {
            logger.error("处理请求"+requestPath+"发生异常:",ex);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        finally {

        }
    }
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                       @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                       @Nullable Exception exception) throws Exception {

        boolean errorView = false;

        if (exception != null) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            logger.error("发生异常:",exception);
        }
        // Did the handler return a view to render?
        if (mv != null) {
            render(mv, request, response);
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                        "': assuming HandlerExecutor completed request handling");
            }
        }

        if (mappedHandler != null) {
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        View view;
        String viewName = mv.getViewName();
        Locale locale =null;
        if (viewName != null) {
            // We need to resolve the view name.
            view = resolveViewName(viewName, mv.getModel(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                        "' in servlet with name '" + getServletName() + "'");
            }
        }
        else {
            // No need to lookup: the ModelAndView object contains the actual View object.
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                        "View object in servlet with name '" + getServletName() + "'");
            }
        }

        // Delegate to the View object for rendering.
        if (logger.isDebugEnabled()) {
            logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        try {
//            response.setStatus(mv.getStatus());
            view.render(mv.getModel(), request, response);
        }
        catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                        getServletName() + "'", ex);
            }
            throw ex;
        }
    }

    protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                                   Locale locale, HttpServletRequest request) throws Exception {

        if (this.viewResolvers != null) {
            for (ViewResolver viewResolver : this.viewResolvers) {
                View view = viewResolver.resolveViewName(viewName, locale);
                if (view != null) {
                    return view;
                }
            }
        }
        return null;
    }
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
            for (HandlerMapping hm : this.handlerMappings) {
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
    protected HandlerExecutor getHandlerExecutor(Object handler) throws ServletException {
        if (this.handlerExecutors != null) {
            for (HandlerExecutor executor : this.handlerExecutors) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Testing handler executor [" + executor + "]");
                }
                if (executor.supports(handler)) {
                    return executor;
                }
            }
        }
        throw new ServletException("No executor for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerExecutor that supports this handler");
    }
}
